package com.mspbots.sync.wise.client;

import cn.hutool.core.util.ReUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.mspbots.api.BadPostException;
import com.mspbots.api.BadRequestException;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.ProxyProvider;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Map;

/**
 * com.mspbots.sync.wise.client.AutoPagerClient
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2020/7/3
 */
@Log4j2
@Service
public class CwWebClient {

    private final static String CW_CLIENT_ID = "b9feb0a1-787c-46b9-9d85-b6f60e2b927a";

    private final WebClient webClient;

    public CwWebClient(WebClient.Builder builder) {

        HttpClient httpClient = HttpClient.create()
                .tcpConfiguration(tcpClient ->
                        tcpClient.proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP).host("127.0.0.1").port(65224)));
        ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);

        this.webClient = builder.clientConnector(connector)
                .defaultHeader("Accept", "application/vnd.connectwis.com+json")
                .defaultHeader("ClientId", CW_CLIENT_ID)
                .build();
    }

    public Mono<JsonNode> getRepository(String path, MultiValueMap<String, String> queryParams) {
        return Mono.subscriberContext().flatMap(ctx -> {
            ClientProperties properties = ctx.get("properties");
            return this.request(UriComponentsBuilder.newInstance().scheme("https")
                    .host(properties.getSite()).path("/v4_6_release/apis/3.0/")
                    .path(path).queryParams(queryParams).build().toUriString());
        }).flatMap(clientResponse -> clientResponse.bodyToMono(JsonNode.class));
    }

    public Mono<JsonNode> putRepository(String path, Object body) {
        return Mono.subscriberContext().flatMap(ctx -> {
            ClientProperties properties = ctx.get("properties");
            return this.webClient.put().uri(uriBuilder -> uriBuilder.scheme("https").host(properties.getSite())
                    .path("/v4_6_release/apis/3.0/").path(path).build())
                    .accept(MediaType.APPLICATION_JSON)
                    .bodyValue(body)
                    .retrieve()
                    .onStatus(HttpStatus::isError, clientResponse ->
                            clientResponse.bodyToMono(Map.class)
                                    .map(m -> new BadRequestException(String.valueOf(m.get("message")))))
                    .bodyToMono(JsonNode.class);
        });
    }

    public Mono<JsonNode> deleteRepository(String path, String id) {
        return Mono.subscriberContext().flatMap(ctx -> {
            ClientProperties properties = ctx.get("properties");
            return this.webClient.delete().uri(uriBuilder -> uriBuilder.scheme("https").host(properties.getSite())
                    .path("/v4_6_release/apis/3.0/").path(path).build(id))
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .onStatus(HttpStatus::isError, clientResponse ->
                            clientResponse.bodyToMono(Map.class)
                                    .map(m -> new BadRequestException(String.valueOf(m.get("message")))))
                    .bodyToMono(JsonNode.class);
        });
    }

    public Mono<JsonNode> postRepository(String path, Object body) {
        return Mono.subscriberContext().flatMap(ctx -> {
            ClientProperties properties = ctx.get("properties");
            return this.webClient.post().uri(uriBuilder -> uriBuilder.scheme("https").host(properties.getSite())
                    .path("/v4_6_release/apis/3.0/").path(path).build())
                    .accept(MediaType.APPLICATION_JSON)
                    .bodyValue(body)
                    .retrieve().onStatus(HttpStatus::isError, clientResponse ->
                            clientResponse.bodyToMono(Map.class)
                                    .map(m -> new BadPostException(String.valueOf(m.get("message")))))
                    .bodyToMono(JsonNode.class);
        });
    }

    public Flux<JsonNode> fetch(String path, MultiValueMap<String, String> params) {
        return Mono.subscriberContext().flatMapMany(ctx -> {
            ClientProperties properties = ctx.get("properties");
            return this.fetchUrl(UriComponentsBuilder.newInstance().scheme("https")
                    .host(properties.getSite()).path("/v4_6_release/apis/3.0/").path(path).queryParams(params).build().toUriString());
        });

    }

    public Flux<JsonNode> fetchUrl(String url) {
        return this.request(url)
                .delayElement(Duration.ofMillis(200))
                .expand(this::autoNextPage)
                .flatMap(clientResponse -> clientResponse.bodyToFlux(JsonNode.class));
    }

    private Mono<ClientResponse> request(String uri) {
        log.debug("Auto pager request url {}", uri);
        return Mono.subscriberContext().flatMap(ctx -> {
            ClientProperties properties = ctx.get("properties");
            return this.webClient.get().uri(uri)
                    .headers(headers -> headers
                            .setBasicAuth(properties.getAppId() + "+" + properties.getPublicKey(),
                                    properties.getPrivateKey()))
                    .exchange();
        }).onErrorResume(err -> {
            log.error("Auto pager request url {}, fetch error msg: {}.", uri, err.getMessage());
            return Mono.empty();
        });
    }

    private Mono<ClientResponse> autoNextPage(ClientResponse resp) {
        List<String> links = resp.headers().header("link");
        if (links.size() == 0) {
            return Mono.empty();
        }
        String newUrl = ReUtil.get("<([^<]+?)>; rel=\"next\"", links.get(0), 1);
        if (StringUtils.isEmpty(newUrl)) {
            return Mono.empty();
        }
        return this.request(URLDecoder.decode(newUrl, StandardCharsets.UTF_8));
    }


}
